#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"

#define BUFF_SIZE 800000 // 800kB we need enough space
char send_buff[BUFF_SIZE];
char file_buff[BUFF_SIZE];

void handle_https_request(SSL* ssl)
{
	int startc=0,endc=0,noend=0;
    // const char* response="HTTP/1.0 200 OK\r\nContent-Length: 27\r\n\r\nCNLab 2: Socket programming";
    
	// use this string to say 404 not found
	const char* notFoundResp = "HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n";
	int send_len = 0;
	if (SSL_accept(ssl) == -1){
		perror("SSL_accept failed");
		exit(1);
	}
    else {
		char buf[1024] = {0};
        int bytes = SSL_read(ssl, buf, sizeof(buf));
		if (bytes < 0) {
			perror("SSL_read failed");
			exit(1);
		}
		// printf("read data:\n");
		// printf("%s\n",buf);
		
		// check the header request
		char method[16];
		char path[128];
		char version[16];

		sscanf(buf,"%s %s %s",method,path,version);

		// check whether the request has a range
		char range[128];	// the range string
		int partial;	//indicate that the request is partial
		const char * range_prompt = "Range: ";	// the range indicator
		char * range_start;

		// check "Range: 100-200" etc
		partial = 0;

		// if the string has "Range: xxx", then the client request for partial file
		if((range_start = strstr(buf,range_prompt)) != NULL)
		{
			partial = 1;
			sscanf(range_start + strlen(range_prompt),"%s",range);
			// printf("Range is %s\n",range);

			// check 100- (start, no end) or 100-200 (start, has an end)
			int input_num;
			if((input_num = sscanf(range,"bytes=%d-%d",&startc ,&endc))==1)	// like 100-
			{
				noend = 1;
				// printf("want from %d\n",startc);
			}
			else // like 100-200
			{
				// nothing todo
				// printf("want between %d and %d\n",startc,endc);
			}
			
		}

		// try to open the file and send data
		FILE * req_file = fopen(path + 1, "r");
		if(req_file == NULL)
		{
			// printf("File %s not found\n",path);
			// 404 not found
			SSL_write(ssl, notFoundResp, strlen(notFoundResp));
		}
		else
		{
			// get file length
			send_len = 0;
			// printf("wanted: %s\n",path);
			// char c;	
			// get the part of the file that needs to be transfered
			fseek(req_file,0,SEEK_END);
			long int file_len = ftell(req_file);
			long int partial_file_len = file_len;
			char ch;
			int len = 0;
			fseek(req_file,0,SEEK_SET);
			if(partial)
			{
				// printf("Yes, partial\n");
				fseek(req_file,startc,SEEK_SET);
				if(noend)
				{
					partial_file_len = (file_len - startc);
					while((ch = fgetc(req_file))!= EOF && len < BUFF_SIZE)
					{
						file_buff[len] = ch;
						len ++;
					}
					file_buff[len] = 0;
				}
				else
				{
					partial_file_len = endc - startc + 1; 
					while((ch = fgetc(req_file))!= EOF && len < BUFF_SIZE && len < partial_file_len)
					{
						file_buff[len] = ch;
						len ++;
					}
					file_buff[len] = 0;
					// printf("get partial length: %ld\n",strlen(file_buff));
				}
			}
			else
			{
				while((ch = fgetc(req_file))!= EOF && len < BUFF_SIZE)
					{
						file_buff[len] = ch;
						len ++;
					}
				file_buff[len] = 0;
			}
			send_len = sprintf(send_buff,"HTTP/1.0 %d OK\r\nContent-Length: %ld\r\n\r\n",partial?206:200,partial_file_len);
			
			// while((c = fgetc(req_file))!=EOF)
			// {
			// 	putchar(c);
			// 	send_buff[send_len] = c;
			// 	send_len ++;
			// }
			
			

			fclose(req_file);
			send_len += sprintf(send_buff + send_len,"%s",file_buff);
			SSL_write(ssl, send_buff, send_len);
		}
    }
    int sock = SSL_get_fd(ssl);
    SSL_free(ssl);
    close(sock);
}

// only redirect to https
void * http_thread(void * arg)
{
	// while(1)
	// {
	// 	sleep(5);
	// 	printf("I am awake\n");
	// }
	struct sockaddr_in server;
	int sockfd = socket(AF_INET, SOCK_STREAM,0);
	char * recv_buf_http = malloc(BUFF_SIZE);
	char * send_buff_http = malloc(BUFF_SIZE);
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons(80);
	bind(sockfd,(struct sockaddr *)&server,sizeof(server));
	listen(sockfd,10);
	// printf("Serving HTTP on port 80\n");
	while(1)
	{
		socklen_t clen = sizeof(server);
		int csock = accept(sockfd,(struct sockaddr *)&server,&clen);
		int recv_len = recv(csock,recv_buf_http,BUFF_SIZE,0);
		// printf("csock: %d\nsockfd: %d\n",csock,sockfd);
		if(recv_len < 0)
		{
			// printf("Error at receiving of HTTP\n");
		}
		char method[16];
		char path[128];
		char version[16];
		sscanf(recv_buf_http,"%s %s %s",method,path,version);
		// not robust
		int len = sprintf(send_buff_http,"HTTP/1.0 301 Moved Permanently\r\nLocation: https://10.0.0.1%s\r\nContent-Length: 0\r\n\r\n",path);
		send(csock,send_buff_http,len,0);
		close(csock);
	}
	close(sockfd);
	free(recv_buf_http);
	return NULL;
}


int main()
{
	// create another thread to serve http on port 80
	pthread_t thr_http;
	pthread_create(&thr_http,NULL,http_thread,NULL);

	// init SSL Library
	// SSL: secure socket layer
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	SSL_load_error_strings();

	// enable TLS method
	// TLS: Transport Layer Security
	const SSL_METHOD *method = TLS_server_method();
	SSL_CTX *ctx = SSL_CTX_new(method);

	// load certificate and private key
	if (SSL_CTX_use_certificate_file(ctx, "./keys/cnlab.cert", SSL_FILETYPE_PEM) <= 0) {
		perror("load cert failed");
		exit(1);
	}
	if (SSL_CTX_use_PrivateKey_file(ctx, "./keys/cnlab.prikey", SSL_FILETYPE_PEM) <= 0) {
		perror("load prikey failed");
		exit(1);
	}

	// init socket, listening to port 443
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("Opening socket failed");
		exit(1);
	}
	int enable = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
		perror("setsockopt(SO_REUSEADDR) failed");
		exit(1);
	}

	struct sockaddr_in addr;
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(443);

	if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
		perror("Bind failed");
		exit(1);
	}
	listen(sock, 10);

	while (1) {
		struct sockaddr_in caddr;
		socklen_t len;
		int csock = accept(sock, (struct sockaddr*)&caddr, &len);
		if (csock < 0) {
			perror("Accept failed");
			exit(1);
		}
		SSL *ssl = SSL_new(ctx); 
		SSL_set_fd(ssl, csock);
		handle_https_request(ssl);
	}

	close(sock);
	SSL_CTX_free(ctx);

	return 0;
}

